#ifndef GENETICTRAINING_H
#define GENETICTRAINING_H

#include "GeneticTraining.h"

#include "randomlib.h"
#include "fstream"
#include "iostream"

using namespace std;

GeneticTraining::GeneticTraining(int numOfLayers , int* neuronsNum ,int MaxPopulation, 
								 float mutRate , float crossRate ){

			mPopulationSize		= MaxPopulation;
			mfMutationRate		= mutRate;
			mfCrossoverRate		= crossRate;
			mLayerNum			= numOfLayers;
			mGenerationNumber	= 0;
			trainInput			= 0;
			desiredOutput		= 0;
			mActualSize			= 0;
			mSelectedSize		= MaxPopulation/2;
			
			//create neural network - attributes include input and output layers
			ann = new ArtificialNeuralNetwork(numOfLayers,neuronsNum);

			//save neuron numbers in each layer
			mNeuronNum = new int[numOfLayers];
			for(int i = 0 ; i < numOfLayers ; i++)
				mNeuronNum[i] = neuronsNum[i];


			//find the total connection number between neurons 
			 mWeightConNum = 0;			
			 for (int i = 1 ; i < numOfLayers ; i++){
				 mWeightConNum +=neuronsNum[i] * neuronsNum[i-1] + mNeuronNum[i];
			 }

			//mPopulationSize = 3 * mWeightConNum; //DEGISTR SONRA>>>

			 //create fitness vector
			 mFitnessValues = new double[mPopulationSize]; //fitness i is the fitness value related to i th individual mChromosomes[i] 

			 //create population vectors
			 mChromosomes = new Chromosome[mPopulationSize];
			 for(int i = 0 ; i < mPopulationSize ; i++){
				 mChromosomes[i] = new double[mWeightConNum];
				mFitnessValues[i] = 0; //CAREFUL!! maybe u should initialize to some other value
			 }

			 //read training data to memory
			datasets = 	DatasetReader::getInstance()->parseFile<double>("sin_0_25.dat",INPUT_NEURON_NUMBER,OUTPUT_NEURON_NUMBER,TYPE::DOUBLE);
			
}
GeneticTraining::~GeneticTraining(){}



void GeneticTraining::calculateAllFitnessOfPopulation (){

	for(int i = 0 ; i < mPopulationSize ; i++){
		mFitnessValues[i] = getFitness (mChromosomes[i]);
	}
	//find the best individual and save it
	double best = mFitnessValues[0];

	int temp = 0;
	

	for(int i = 1 ; i < mPopulationSize ; i++){
		if(mFitnessValues[i] < best){
			best = mFitnessValues[i];
			temp = i;
		}
			
	}
	bestIndividual  = temp;
}

void GeneticTraining::printPopulation(){

	std::ofstream out("population.txt",ios::app);
	out<<"Generation = "<<mGenerationNumber<< endl;
	for(int i = 0 ; i < mPopulationSize ; i++){
		if(mChromosomes[i] == 0){
			out<<"Chromosome "<<i<<" is empty!"<<endl;
		}else{
			out<<"Chromosome "<<i<<" = ";
			for(int j = 0 ; j < mWeightConNum ; j++){
				out<<mChromosomes[i][j]<<" ";
			}
			out<<endl;
		}
	}
	out<<"----------------------------"<<endl;
	for(int i = 0 ; i < mPopulationSize ; i++)
		out<<"Fitness  of "<<i<<" th chromosome is : "<<mFitnessValues[i]<<endl;

	out.close ();
}

void GeneticTraining::printFitness (){

	cout<<"generation: "<<mGenerationNumber<<"  min fitness value: "<<getMinFitness () <<endl;
}

void  GeneticTraining::initializePopulation(){

	
	RandomInitialise (1802,19373);

	for(int i = 0 ; i < mPopulationSize ; i++){
		for(int j = 0 ; j < mWeightConNum ; j++){
			mChromosomes[i][j] = RandomDouble(MIN_WEIGHT, MAX_WEIGHT);
		}
	}
	calculateAllFitnessOfPopulation ();
	


}
Chromosome	GeneticTraining::mutate(int index){

	
	if(index == bestIndividual) return mChromosomes[index];

	int r1,r2,r3;
	do{
		r1 = RandomInt (0,mPopulationSize-1);
		r2 = RandomInt (0,mPopulationSize-1);
		r3 = RandomInt (0,mPopulationSize-1);

	}while(r1 == r2 || r1 == r3 || r2 == r3 || r1 == index || r2 == index || r3 == index);

	Chromosome diff = new double[mWeightConNum];
	
	
	for(int i = 0 ; i < mWeightConNum ; i++){
		diff[i] = mChromosomes[r1][i] + (MUTATION_RATE * (mChromosomes[r3][i] - mChromosomes[r2][i]));
	}

	return diff;

}
Chromosome	GeneticTraining::crossover(Chromosome source, Chromosome trial){
	

	Chromosome  crossedTrialIndividual = new double[mWeightConNum];

	for(int i = 0 ; i < mWeightConNum ; i++){

		double ran = RandomDouble (0.0, 1.0);
		if(ran < CROSSOVER_RATE){
			crossedTrialIndividual[i] = trial[i];
		}else{
			crossedTrialIndividual[i] = source[i];
		}
	}
	return crossedTrialIndividual;
}

Chromosome GeneticTraining::newIndividual (){
	Chromosome c = new double[mWeightConNum];

	for(int i = 0 ; i < mWeightConNum ; i++){
		c[i] = RandomDouble (MIN_WEIGHT, MAX_WEIGHT);
	}

	return c;
}

double		GeneticTraining::getFitness(Chromosome individual){

	ann->loadWights (individual);
	double sumError = 0;

trainInput = new double[datasets[0].inputs.size()];
desiredOutput  = new double[datasets[0].outputs.size()];

	for(int i = 0 ; i < datasets.size () ; i++){ //for each training sample

	
		
		for(int inData = 0 ; inData < datasets[i].inputs.size() ; inData++)
			*(trainInput+inData) = datasets[i].inputs[inData];

		
		
		for(int outData = 0 ; outData < datasets[i].outputs.size() ; outData++)
			*(desiredOutput+outData) = datasets[i].outputs[outData];

		ann->feedForward (trainInput);
		sumError += ann->getMeanSquareError (desiredOutput);
		
	}	
	delete[] trainInput;
	delete[] desiredOutput;
	
		return sumError;	
}
void		GeneticTraining::setANNweightsWithBestChromosome(){
ann->loadWights (bestChromosome);
	
}
double* GeneticTraining::getANNresult(double* input){

	

	double* inp = new double[INPUT_NEURON_NUMBER];
	for(int i = 0 ; i < INPUT_NEURON_NUMBER; i++){
		*(inp + i) = input[i];
	}

	ann->feedForward (inp);
	

	double *o = new double[OUTPUT_NEURON_NUMBER];
	for(int i = 0 ; i < OUTPUT_NEURON_NUMBER ; i++){
		*(o + i) = ann->getOutput (i);
	}

	return o;
}
Chromosome		GeneticTraining::select(Chromosome original, Chromosome trial){

	
		calculateAllFitnessOfPopulation ();
	if(getFitness (trial) < getFitness (original))
		return trial;

	return original;
}
//each cycle consist on mutation,crossover and select operations for every individual
void		GeneticTraining::cycle (){

	Chromosome m;
	Chromosome c;
	Chromosome s;

	for(int i = 0 ; i < mPopulationSize ; i++){
		m = mutate (i);
		c = crossover (mChromosomes[i],m);
		s = select (mChromosomes[i], c);
		mChromosomes[i] = s;
		//mChromosomes[i] = select (mChromosomes[i], crossover (mChromosomes[i],mutate (i)));
	}

	printFitness ();
	++mGenerationNumber;
}

double GeneticTraining::getMinFitness(){	
	
	
	double min = mFitnessValues[0];
	for(int i = 1 ; i < mPopulationSize ; i++){
		if(mFitnessValues[i] < min){
			min = mFitnessValues[i];
			bestIndividual = i;
		}
	}
	bestChromosome = mChromosomes[bestIndividual];
	return min;
}

void		GeneticTraining::release (){
	delete [] mChromosomes;
	delete [] mFitnessValues;
	delete [] mNeuronNum; 
	ann->release ();
}

#include "randomlib.c"


#endif